
#include "HashTermino.h"

HashTermino::HashTermino(int tam_bloque, const string& ruta_arch_bloques, const string& ruta_arch_esp_libre,
		const string& ruta_arch_tabla, const string& ruta_arch_temporal, const string& ruta_arch_ID) {
	this->handler_bloques.set_tam_bloque(tam_bloque);
	this->handler_bloques.set_ruta_arch_bloques(ruta_arch_bloques);

	HandlerEspLibre handler_esp_libre(ruta_arch_esp_libre);
	this->handler_bloques.set_handler_esp_libre(handler_esp_libre);

	this->handler_tabla.set_ruta_arch_tabla(ruta_arch_tabla);
	this->handler_tabla.set_ruta_arch_temporal(ruta_arch_temporal);

	this->handler_ID.set_ruta_arch_ID(ruta_arch_ID);

	this->crear_condiciones_iniciales();
}

void HashTermino::crear_condiciones_iniciales() {
	this->handler_bloques.crear_arch_vacio();
	this->handler_bloques.get_handler_esp_libre().crear_arch_inicial();
	this->handler_tabla.crear_tabla_inicial();
	this->handler_ID.crear_arch_inicial();

	if (this->handler_tabla.tabla_vacia() == true) {
		Bucket bloque;
		unsigned int offset = 0;
		char buffer[TAM_CUBO];

		bloque.serializar(buffer, offset);
		int num_bloque = this->handler_bloques.guardar_bloque(buffer);
		this->handler_tabla.insertar_primer_referencia(num_bloque);
	}
}

int HashTermino::funcion_hash(const string& termino) {
	int clave = 0;

	for (unsigned int i = 0; i < termino.size(); ++ i)
		clave += ((int)termino[i]) * (termino.size() - i);

	return clave;
}

void HashTermino::insertar_reg(RegIndice& reg) {
	Bucket bloque;
	unsigned int offset = 0;
	char buffer[TAM_CUBO];

	int pos_tabla_bloque = this->handler_tabla.get_pos_tabla(reg.get_clave());
	int num_bloque = this->handler_tabla.get_num_bloque(reg.get_clave());
	this->handler_bloques.recuperar_bloque(buffer, num_bloque);
	bloque.hidratar(buffer, offset);

	if (bloque.entra_en_bloque(reg) == true) {
		bloque.agregar_nuevo_reg(reg);
		offset = 0;
		bloque.serializar(buffer, offset);
		this->handler_bloques.guardar_bloque(buffer, num_bloque);
	}
	else {
		int tam_disp_inicial = bloque.get_tam_disp();
		bloque.duplicar_tam_disp();
		Bucket nuevo_bloque(bloque.get_tam_disp());

		offset = 0;
		nuevo_bloque.serializar(buffer, offset);
		int num_nuevo_bloque = this->handler_bloques.guardar_bloque(buffer);

		if (tam_disp_inicial == this->handler_tabla.get_tam_tabla()) {
			this->handler_tabla.duplicar_tabla();
			this->handler_tabla.reemplazar_referencia(num_bloque, num_nuevo_bloque);
		}
		else this->handler_tabla.reemplazar_referencias(pos_tabla_bloque, num_nuevo_bloque, nuevo_bloque);

		list < RegIndice > regs_desactualizados = bloque.actualizar_regs(num_bloque, this->handler_tabla);

		if (regs_desactualizados.empty() == false) {
			nuevo_bloque.incorporar_regs(regs_desactualizados);
			offset = 0;
			nuevo_bloque.serializar(buffer, offset);
			this->handler_bloques.guardar_bloque(buffer, num_nuevo_bloque);
		}

		offset = 0;
		bloque.serializar(buffer, offset);
		this->handler_bloques.guardar_bloque(buffer, num_bloque);

		this->insertar_reg(reg);
	}
}

bool HashTermino::elemento_repetido(RegIndice& reg, const string& termino) {
	if (reg.existe_elemento(termino) == true)
		return true;

	if (reg.existe_elemento(termino) == false && reg.get_bloque_sig() == -1)
		return false;

	if (reg.existe_elemento(termino) == false && reg.get_bloque_sig() != -1) {
		Bucket bloque_sig;

		unsigned int offset = 0;
		char buffer[TAM_CUBO];

		this->handler_bloques.recuperar_bloque(buffer, reg.get_bloque_sig());
		bloque_sig.hidratar(buffer, offset);

		RegIndice& reg_aux = bloque_sig.buscar_reg(reg.get_clave());

		return this->elemento_repetido(reg_aux, termino);
	}
}

void HashTermino::agregar_nuevo_elemento(Bucket& bloque, int num_bloque, RegIndice& reg, const string& termino, int ID) {
	unsigned int offset;
	char buffer[TAM_CUBO];

	if (this->elemento_repetido(reg, termino) == false) {
		if (bloque.entra_en_bloque(termino, ID) == true) {
			Elemento elemento(termino, ID);
			reg.agregar_nuevo_elemento(elemento);

			offset = 0;
			bloque.serializar(buffer, offset);
			this->handler_bloques.guardar_bloque(buffer, num_bloque);
		}
		else {
			Bucket bloque_sig;

			if (reg.get_bloque_sig() == -1) {
				RegIndice reg_aux(reg.get_clave());

				Elemento elemento(termino, ID);
				reg_aux.agregar_nuevo_elemento(elemento);

				bloque_sig.agregar_nuevo_reg(reg_aux);

				offset = 0;
				bloque_sig.serializar(buffer, offset);
				int num_bloque_sig = this->handler_bloques.guardar_bloque(buffer);

				reg.set_bloque_sig(num_bloque_sig);

				offset = 0;
				bloque.serializar(buffer, offset);
				this->handler_bloques.guardar_bloque(buffer, num_bloque);
			}
			else {
				offset = 0;
				this->handler_bloques.recuperar_bloque(buffer, reg.get_bloque_sig());
				bloque_sig.hidratar(buffer, offset);

				RegIndice& reg_aux = bloque_sig.buscar_reg(reg.get_clave());

				this->agregar_nuevo_elemento(bloque_sig, reg.get_bloque_sig(), reg_aux, termino, ID);
			}
		}
	}
}

int HashTermino::alta(const string& termino) {
	Bucket bloque;
	unsigned int offset = 0;
	char buffer[TAM_CUBO];

	int clave = this->funcion_hash(termino);

	int ID = this->handler_ID.consultar_archivo();
	this->handler_ID.modificar_archivo();

	int num_bloque = this->handler_tabla.get_num_bloque(clave);
	this->handler_bloques.recuperar_bloque(buffer, num_bloque);
	bloque.hidratar(buffer, offset);

	if (bloque.existe_reg(clave) == true)
		this->agregar_nuevo_elemento(bloque, num_bloque, bloque.buscar_reg(clave), termino, ID);
	else {
		Elemento elemento(termino, ID);
		RegIndice reg(clave);
		reg.agregar_nuevo_elemento(elemento);
		this->insertar_reg(reg);
	}

	return ID;
}

bool HashTermino::eliminar_reg(int clave) {
	Bucket bloque;
	unsigned int offset = 0;
	char buffer[TAM_CUBO];

	int pos_tabla_bloque = this->handler_tabla.get_pos_tabla(clave);
	int num_bloque = this->handler_tabla.get_num_bloque(clave);
	this->handler_bloques.recuperar_bloque(buffer, num_bloque);
	bloque.hidratar(buffer, offset);

	if (!bloque.eliminar_reg(clave))
		return false;

	if (bloque.esta_vacio() == true) {
		int num_otro_bloque = this->handler_tabla.puedo_liberar_bloque(bloque, pos_tabla_bloque);
		if (num_otro_bloque != -1) {
			Bucket otro_bloque;

			offset = 0;
			this->handler_bloques.recuperar_bloque(buffer, num_otro_bloque);
			otro_bloque.hidratar(buffer, offset);
			this->handler_tabla.liberar_referencias(pos_tabla_bloque, num_otro_bloque, otro_bloque);

			otro_bloque.truncar_tam_disp();
			offset = 0;
			otro_bloque.serializar(buffer, offset);
			this->handler_bloques.guardar_bloque(buffer, num_otro_bloque);
			this->handler_bloques.eliminar_bloque(num_bloque);

			if (this->handler_tabla.mitades_iguales() == true)
				this->handler_tabla.truncar_tabla();
		}
		else {
			bloque.get_regs().clear();
			offset = 0;
			bloque.serializar(buffer, offset);
			this->handler_bloques.guardar_bloque(buffer, num_bloque);
		}
	}
	else {
		offset = 0;
		bloque.serializar(buffer, offset);
		this->handler_bloques.guardar_bloque(buffer, num_bloque);
	}

	return true;
}

void HashTermino::obtener_reg(RegIndice& reg, Bucket& bloque_sig, list < int > & bloques_sigs, int clave) {
	char buffer[TAM_CUBO];
	unsigned int offset_aux = 0;

	this->handler_bloques.recuperar_bloque(buffer, reg.get_bloque_sig());
	bloque_sig.hidratar(buffer, offset_aux);

	bloques_sigs.push_back(reg.get_bloque_sig());
	reg = bloque_sig.buscar_reg(clave);
}

void HashTermino::eliminar_reg_y_bloques_sigs(Bucket& bloque, int num_bloque, int clave) {
	if (bloque.existe_reg(clave) == true) {
		RegIndice& reg = bloque.buscar_reg(clave);

		if (reg.esta_vacio() == true)
			this->eliminar_reg(clave);
		else {
			if (reg.get_elementos().empty() == true && reg.get_bloque_sig() != -1) {
				Bucket bloque_sig;
				list < int > bloques_sigs;

				this->obtener_reg(reg, bloque_sig, bloques_sigs, clave);

				while (reg.get_elementos().empty() == true && reg.get_bloque_sig() != -1)
					this->obtener_reg(reg, bloque_sig, bloques_sigs, clave);

				if (reg.get_elementos().empty() == true && reg.get_bloque_sig() == -1) {
					list < int > ::iterator it;
					for (it = bloques_sigs.begin(); it != bloques_sigs.end(); ++ it)
						this->handler_bloques.eliminar_bloque(*it);
					this->eliminar_reg(clave);
				}
			}
		}
	}

}

void HashTermino::eliminar_elemento(Bucket& bloque, int num_bloque, int clave, const string& termino) {
	unsigned int offset_aux;
	char buffer[TAM_CUBO];

	if (bloque.existe_reg(clave) == true) {
		RegIndice& reg = bloque.buscar_reg(clave);

		if (reg.existe_elemento(termino) == false) {
			if (reg.get_bloque_sig() == -1)
				return;
			else {
				Bucket bloque_sig;

				offset_aux = 0;
				this->handler_bloques.recuperar_bloque(buffer, reg.get_bloque_sig());
				bloque_sig.hidratar(buffer, offset_aux);

				this->eliminar_elemento(bloque_sig, reg.get_bloque_sig(), clave, termino);
			}
		}
		else {
			reg.eliminar_elemento(termino);

			offset_aux = 0;
			bloque.serializar(buffer, offset_aux);
			this->handler_bloques.guardar_bloque(buffer, num_bloque);
		}
	}
}

void HashTermino::baja(const string& termino) {
	Bucket bloque;
	unsigned int offset_aux = 0;
	char buffer[TAM_CUBO];

	int clave = this->funcion_hash(termino);

	int num_bloque = this->handler_tabla.get_num_bloque(clave);
	this->handler_bloques.recuperar_bloque(buffer, num_bloque);
	bloque.hidratar(buffer, offset_aux);

	this->eliminar_elemento(bloque, num_bloque, clave, termino);
	this->eliminar_reg_y_bloques_sigs(bloque, num_bloque, clave);
}

int HashTermino::consultar_elemento(Bucket& bloque, int num_bloque, int clave, const string& termino) {
	unsigned int offset_aux;
	char buffer[TAM_CUBO];

	if (bloque.existe_reg(clave) == true) {
		RegIndice& reg = bloque.buscar_reg(clave);

		if (reg.existe_elemento(termino) == false) {
			if (reg.get_bloque_sig() == -1)
				return -1;
			else {
				Bucket bloque_sig;

				offset_aux = 0;
				this->handler_bloques.recuperar_bloque(buffer, reg.get_bloque_sig());
				bloque_sig.hidratar(buffer, offset_aux);

				return this->consultar_elemento(bloque_sig, reg.get_bloque_sig(), clave, termino);
			}
		}
		else {
			Elemento& elemento = reg.buscar_elemento(termino);
			return elemento.get_ID();
		}
	}
}

int HashTermino::consultar(const string& termino) {
	Bucket bloque;
	unsigned int offset_aux = 0;
	char buffer[TAM_CUBO];

	int clave = this->funcion_hash(termino);

	int num_bloque = this->handler_tabla.get_num_bloque(clave);
	this->handler_bloques.recuperar_bloque(buffer, num_bloque);
	bloque.hidratar(buffer, offset_aux);

	return this->consultar_elemento(bloque, num_bloque, clave, termino);
}

void HashTermino::mostrar(const string& nombre_arch) {
	ofstream arch;
	Bucket bloque;
	char buffer[TAM_CUBO];
	unsigned int offset;
	int cant_bloques = this->handler_bloques.get_tam_arch_bloques() / TAM_CUBO;

	arch.open(nombre_arch.c_str(), fstream::out);

	arch << "********************************************************************************" << endl;
	arch << "		             Dispersión Extensible de Términos                           " << endl;
	arch << "********************************************************************************" << endl;
	arch << endl;

	for (int i = 0; i < cant_bloques; ++ i) {
		arch << "N° " << i << ":" << endl;
		offset = 0;
		this->handler_bloques.recuperar_bloque(buffer, i);
		bloque.hidratar(buffer, offset);
		bloque.toString(arch);
		arch << endl;
	}

	arch.close();
}

void HashTermino::mostrar() {
	Bucket bloque;
	char buffer[TAM_CUBO];
	unsigned int offset;
	int cant_bloques = this->handler_bloques.get_tam_arch_bloques() / TAM_CUBO;

	cout << "********************************************************************************" << endl;
	cout << "		             Dispersión Extensible de Términos                           " << endl;
	cout << "********************************************************************************" << endl;
	cout << endl;

	for (int i = 0; i < cant_bloques; ++ i) {
		cout << "N° " << i << ":" << endl;
		offset = 0;
		this->handler_bloques.recuperar_bloque(buffer, i);
		bloque.hidratar(buffer, offset);
		bloque.toString(cout);
		cout << endl;
	}
}
